// -----------------------------------------------------------------------------
// <copyright file="Program.cs" company="Justin Wallace Saunders">
//     Copyright (c) Microsoft Public License. All rights reserved.
// </copyright>
// <author name="Justin Wallace Saunders" email="justinsa@live.ca" />
// -----------------------------------------------------------------------------
namespace Community.Tools.Database.Builder
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Threading;

    /// <summary>
    /// The primary console execution class.
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// Executable entry point.
        /// </summary>
        /// <param name="args">The command line arguments</param>
        /// <returns>Exit code: 0 = success; otherwise failure</returns>
        internal static int Main(string[] args)
        {
            if (args == null || args.Length == 0 || args[0] == "-?" || args[0] == "/?")
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(Settings.Usage);
                Console.ResetColor();
                return -1;
            }

            foreach (string parameter in args)
            {
                string value = parameter.Trim('\'');
                if (value.StartsWith("ScriptRoot:", StringComparison.OrdinalIgnoreCase))
                {
                    value = value.Replace("ScriptRoot:", string.Empty);
                    Settings.ScriptRoot = value;
                }
                else if (value.StartsWith("TargetLogRoot:", StringComparison.OrdinalIgnoreCase))
                {
                    value = value.Replace("TargetLogRoot:", string.Empty);
                    Settings.LogRoot = value;
                }
                else if (value.StartsWith("TargetDatabaseRoot:", StringComparison.OrdinalIgnoreCase))
                {
                    value = value.Replace("TargetDatabaseRoot:", string.Empty);
                    Settings.DatabaseRoot = value;
                    Settings.Substitutions.Add("%TargetDatabaseRoot%", value);
                }
                else if (value.StartsWith("TargetDatabaseServer:", StringComparison.OrdinalIgnoreCase))
                {
                    value = value.Replace("TargetDatabaseServer:", string.Empty);
                    Settings.DatabaseServer = value;
                    Settings.Substitutions.Add("%TargetDatabaseServer%", value);
                }
                else if (value.StartsWith("TargetDatabaseName:", StringComparison.OrdinalIgnoreCase))
                {
                    value = value.Replace("TargetDatabaseName:", string.Empty);
                    Settings.DatabaseName = value;
                    Settings.Substitutions.Add("%TargetDatabaseName%", value);
                }
                else if (value.Contains(":"))
                {
                    var tokens = value.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries);
                    if (tokens.Length == 2)
                    {
                        Settings.Substitutions.Add(string.Format(CultureInfo.InvariantCulture, "%{0}%", tokens[0]), tokens[1]);
                    }
                }
            }

            try
            {
                Settings.LoggingEnabled = !string.IsNullOrEmpty(Settings.LogDirectory);
                if (Settings.LoggingEnabled)
                {
                    Directory.CreateDirectory(Settings.LogDirectory);
                }

                if (!string.IsNullOrEmpty(Settings.DatabaseDirectory))
                {
                    Directory.CreateDirectory(Settings.DatabaseDirectory);
                }

                Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Version: {0}", Assembly.GetExecutingAssembly().GetName().Version.ToString()));
                Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Logging Enabled: {0}", Settings.LoggingEnabled));
                Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Error Log Path: {0}", Settings.ErrorLogFilePath));
                Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Log Path: {0}", Settings.LogFilePath));
                Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Script Root: {0}", Settings.ScriptRoot));
                foreach (var substitution in Settings.Substitutions)
                {
                    Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "{0} = {1}", substitution.Key, substitution.Value));
                }

                foreach (string script in Settings.SqlScripts)
                {
                    string scriptPath = script;

                    // In order to allow for the database to be built to any path
                    // or any database name we need to pre-process any SQL script
                    // ending with a *.pp.* and perform a replacement.
                    if (MatchFileTail(script, Settings.FileTails))
                    {
                        Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "Pre-processing Script: {0}", script));

                        // Read the script in to memory
                        string contents = File.ReadAllText(script);

                        // Perform replacements
                        foreach (var substitution in Settings.Substitutions)
                        {
                            contents = contents.Replace(substitution.Key, substitution.Value);
                        }

                        // Write to temporary file
                        scriptPath = Path.GetTempFileName();
                        File.WriteAllText(scriptPath, contents);
                    }

                    if (!ExecuteScriptFile(scriptPath))
                    {
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(e);
                Console.ResetColor();
                return -2;
            }

            return 0;
        }

        /// <summary>
        /// Compares a filename to a list of tails to determine a match.
        /// </summary>
        /// <param name="filename">The filename</param>
        /// <param name="tails">File tail list</param>
        /// <returns>True if the filename ends with one of the tails, false otherwise</returns>
        private static bool MatchFileTail(string filename, IEnumerable<string> tails)
        {
            foreach (string tail in tails)
            {
                if (filename.EndsWith(tail, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Writes a log message to the specified file path.
        /// </summary>
        /// <param name="path">Log file path</param>
        /// <param name="message">Message to be logged</param>
        private static void Log(string path, string message)
        {
            if (Settings.LoggingEnabled && !string.IsNullOrEmpty(message))
            {
                Console.WriteLine(message);
                File.AppendAllText(path, string.Format(CultureInfo.InvariantCulture, @"[{0}]:{1}", DateTime.Now, message));
            }
        }

        /// <summary>
        /// Executes a script file using the SQL command line.
        /// </summary>
        /// <param name="file">Script file path</param>
        /// <returns>True if the script file executed without timing out, false otherwise</returns>
        private static bool ExecuteScriptFile(string file)
        {
            bool result = true;
            Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, @"Execute Script: {0}", file));
            var startInfo = new ProcessStartInfo();
            startInfo.FileName = Settings.DatabaseExecutable;
            startInfo.Arguments = string.Format(CultureInfo.InvariantCulture, Settings.DatabaseExecutableArguments, file);
            startInfo.RedirectStandardError = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            Log(Settings.LogFilePath, string.Format(CultureInfo.InvariantCulture, "{0} {1}", startInfo.FileName, startInfo.Arguments));
            using (Process command = new Process())
            {
                command.StartInfo = startInfo;
                command.Start();
                command.WaitForExit(Settings.ExecutionTimeout);
                if (!command.HasExited)
                {
                    command.Kill();
                    Log(Settings.ErrorLogFilePath, string.Format(CultureInfo.InvariantCulture, "Killed: {0}", startInfo.FileName));
                    result = false;
                }

                Log(Settings.LogFilePath, command.StandardOutput.ReadToEnd());
                if (command.ExitCode != 0)
                {
                    Log(Settings.ErrorLogFilePath, command.StandardError.ReadToEnd());
                }
            }

            return result;
        }
    }
}